package com.datounet.flutterapp.WebSocket;

import android.app.AlertDialog;
import android.app.PendingIntent;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

import com.datounet.flutterapp.Bean.BeanLatLngMsg;
import com.datounet.flutterapp.Bean.BeanOrderState;
import com.datounet.flutterapp.Bean.BeanRespLatLngList;
import com.datounet.flutterapp.Bean.BeanUser;
import com.datounet.flutterapp.Bean.BeanWsBase;
import com.datounet.flutterapp.EventLatLngMsg;
import com.datounet.flutterapp.MainActivity;
import com.datounet.flutterapp.NativeTTSMgr;
import com.datounet.flutterapp.NotifyMgr;
import com.datounet.flutterapp.R;
import com.google.gson.Gson;

import org.greenrobot.eventbus.EventBus;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;

import java.net.URI;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

public class SocketMgr {
    private static String TAG = SocketMgr.class.getSimpleName();
    private static WebSocketClient client = null;
    private static final String TOKEN_NAME = "YT-SHOP-RIDER-TOKEN";


    private static ScheduledExecutorService recService = Executors.newSingleThreadScheduledExecutor();
    private static final int REC_LIMIT = 5;
    private static final int REC_OFFSET = 5;
    private static AtomicBoolean isConnected = new AtomicBoolean(false);
    private static AtomicInteger reCount = new AtomicInteger(0);

    private final static int HANDLE_EVENT_SOCKET_ON_MSG = 0;
    private final static int HANDLE_EVENT_SOCKET_EVENT = 1;
    private final static String PING_STR = "ping";
    private final static String ON_PONG_STR = "pong";
    private static Gson gson = new Gson();

    private static Handler sockEventHandler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message message) {
//            主线程
            switch (message.what) {
                case HANDLE_EVENT_SOCKET_ON_MSG:
//                    MethodChannelPlugin.invokeDartMethodRaw(MethodTypes.SOCKET_ON_MSG_ARRIVED, ((WSEvent) message.obj).msg, null);
                    BeanWsBase beanWsBase = gson.fromJson(((WSEvent) message.obj).msg, BeanWsBase.class);
//                    notify_normal_singLine("纸嘟嘟消息助手提醒您","您有新的订单，请及时处理");
                    if (beanWsBase.type == 999) {
                        int state = gson.fromJson((String) beanWsBase.message, BeanOrderState.class).getStatus();
                        Log.e(TAG, "handleMessage: " + state);

                        switch (state) {
                            case BeanOrderState.ORDER_NEW:
                                NativeTTSMgr.initAndSpeak("您有新的订单，请及时处理");
                                notify_normal_singLine("纸嘟嘟消息助手提醒您", "您有新的订单，请及时处理");
                                break;
                            case BeanOrderState.ORDER_NEW_2:
                                NativeTTSMgr.initAndSpeak("您有新的订单，请及时处理");
                                notify_normal_singLine("纸嘟嘟消息助手提醒您", "您有新的订单，请及时处理");
                                break;
                            case BeanOrderState.ORDER_CANCEL:
                                NativeTTSMgr.initAndSpeak("订单已取消，请及时查看");
                                notify_normal_singLine("纸嘟嘟消息助手提醒您", "订单已取消，请及时查看");
                                break;
                            case BeanOrderState.ORDER_TRAN:
                                NativeTTSMgr.initAndSpeak("您有新的转接订单，请及时查看");
                                notify_normal_singLine("纸嘟嘟消息助手提醒您", "您有新的转接订单，请及时查看");
                                break;
                            default:
                                break;
                        }
                    }else if(beanWsBase.type==12){
                        BeanRespLatLngList JsonLatLngList=gson.fromJson((String) ((WSEvent) message.obj).msg, BeanRespLatLngList.class);
                        List<BeanLatLngMsg> latLngList=new ArrayList<>();
                        for (int i = 0; i < JsonLatLngList.getMessage().getLatLnglist().size(); i++) {
                            latLngList.add(gson.fromJson(JsonLatLngList.getMessage().getLatLnglist().get(i),BeanLatLngMsg.class));
                        }
                        
                        EventBus.getDefault().post(new EventLatLngMsg(latLngList));
                    }

                    break;
                case HANDLE_EVENT_SOCKET_EVENT:
//                    MethodChannelPlugin.invokeDartMethod(MethodTypes.SOCKET_ON_WS_EVENT, ((WSEvent) message.obj).msg, null);
                    switch (((WSEvent) message.obj).event) {
                        case WSEvent.ON_REACH_RECONNECT_LIMIT:
                            showReconnectDialog();
                            break;
                        case WSEvent.ON_CLOSE_NO_NEED_REC:
                            showReconnectDialog();
                            break;
                    }
                    break;
            }
            return false;
        }
    });


    public static void create(String uriStr, String token) {
        dispose();
        Map<String, String> httpHeaders = new HashMap<String, String>();
        httpHeaders.put(TOKEN_NAME, token);
        client = new WebSocketClient(URI.create(uriStr), httpHeaders) {
            @Override
            public void onOpen(ServerHandshake handshakeData) {
                Log.e(TAG, "onOpen: " + client+handshakeData);
                initHeartBeat();
//                callMainThread(HANDLE_EVENT_SOCKET_EVENT, "onOpen: " + client);
                callMainThread(HANDLE_EVENT_SOCKET_EVENT, new WSEvent(WSEvent.ON_OPEN, "onOpen: " + client));

                sendMsg(gson.toJson(new BeanWsBase<>(1, BeanUser.getMap())));
                if (BeanUser.role_id == 4) {
                    sendMsg(gson.toJson(new BeanWsBase<>(5, BeanUser.getMap())));
                } else if (BeanUser.role_id == 3) {
                    sendMsg(gson.toJson(new BeanWsBase<>(9, BeanUser.getMap())));
                }

            }

            @Override
            public void onMessage(String message) {
                Log.e(TAG, "onMessage: " + message);
                if (message.equals(ON_PONG_STR)) {
                    initHeartBeat();
                    return;
                }
//                callMainThread(HANDLE_EVENT_SOCKET_ON_MSG, message);
                callMainThread(HANDLE_EVENT_SOCKET_ON_MSG, new WSEvent(WSEvent.ON_MSG, message));
            }

            @Override
            public void onClose(int code, String reason, boolean remote) {
                Log.e(TAG, "onClose  exit code: " + code + " reason: " + reason + "remote: " + remote);
//                callMainThread(HANDLE_EVENT_SOCKET_EVENT, "onClose  exit code: " + code + " reason: " + reason + "remote: " + remote);
                if (code == 1006) {
                    callMainThread(HANDLE_EVENT_SOCKET_EVENT, new WSEvent(WSEvent.ON_CLOSE_NEED_REC, "onClose  exit code: " + code + " reason: " + reason + "remote: " + remote));
                    reConnectCycled();
                } else {
                    callMainThread(HANDLE_EVENT_SOCKET_EVENT, new WSEvent(WSEvent.ON_CLOSE_NEED_REC, "onClose  exit code: " + code + " reason: " + reason + "remote: " + remote));
                }
            }

            @Override
            public void onError(Exception ex) {
                Log.e(TAG, "*** onError *** :" + ex);
//                callMainThread(HANDLE_EVENT_SOCKET_EVENT, "*** onError *** :" + ex);
                callMainThread(HANDLE_EVENT_SOCKET_EVENT, new WSEvent(WSEvent.ON_ERROR, "*** onError *** :" + ex));
                reConnectCycled();
            }

            @Override
            protected void onSetSSLParameters(SSLParameters sslParameters) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                    super.onSetSSLParameters(sslParameters);
                }
            }
        };
        client.setConnectionLostTimeout(10);

        //        https://github.com/TooTallNate/Java-WebSocket/wiki/No-such-method-error-setEndpointIdentificationAlgorithm
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
//            try {
            client.connect();
//                //Verify
//                HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier();
//                SSLSocket socket = (SSLSocket) client.getSocket();
//                SSLSession s = socket.getSession();
//                String host = Uri.parse(uriStr).getHost();
//
//                if (!hv.verify(host, s)) {
//                    Log.e(TAG, "Expected host:" + host + "found " + s.getPeerPrincipal());
//                    throw new SSLHandshakeException("Expected host:" + host + "found " + s.getPeerPrincipal());
//                } else {
//                    Log.i(TAG, "ssl Success");
//                }
//            } catch (SSLHandshakeException e) {
//                client.close();
//                e.printStackTrace();
//            } catch (Exception e) {
//                e.printStackTrace();
//            }

        } else {
            try {
                if (uriStr.startsWith("wss")) {
                    client.setSocketFactory(getSSLContextFactory());
                }
                client.connect();
            } catch (Exception e) {
                e.printStackTrace();
                Log.e(TAG, "create: connect failed");
            }
        }
    }

    public static void dispose() {
        resetRecState();
        HeartBeatTimer.stop();
        if (client != null) {
            try {
                client.closeBlocking();
                client = null;
                Log.e(TAG, "dispose:");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * Method which returns a SSLContext from a keystore on Android or IllegalArgumentException on error
     *
     * @return a valid SSLContext
     * @throws IllegalArgumentException when some exception occurred
     */
    private static SSLSocketFactory getSSLContextFactory() throws NoSuchAlgorithmException, KeyManagementException {

        SSLContext sslContext;

        sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, new TrustManager[]{
                new X509TrustManager() {
                    @Override
                    public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {

                    }

                    @Override
                    public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {

                    }

                    @Override
                    public X509Certificate[] getAcceptedIssuers() {
                        return new X509Certificate[0];
                    }
                }
        }, new SecureRandom());

        return sslContext.getSocketFactory();
    }

    public static void sendMsg(String msg) {
        if (msg == null || msg.equals("")) {
            return;
        }
        if (client != null && client.isOpen()) {
            try {
                client.send(msg);
            } catch (Exception e) {
                e.printStackTrace();
            }
            Log.e(TAG, "sendMsg:" + msg);

        } else {
            Log.e(TAG, "sendMsg: client is null or not opened");
        }
    }

    private static void reConnectCycled() {
        if (client == null || client.isOpen()) {
            Log.e(TAG, "client == null || client.isOpen()");
            return;
        }
        if (reCount.get() >= REC_LIMIT) {
//            todo 到达重连上限
            callMainThread(HANDLE_EVENT_SOCKET_EVENT, new WSEvent(WSEvent.ON_REACH_RECONNECT_LIMIT, ""));
            resetRecState();
            Log.e(TAG, "reConnectCycled: reCount > recLimit");
            return;
        } else if (isConnected.get()) {
            callMainThread(HANDLE_EVENT_SOCKET_EVENT, new WSEvent(WSEvent.ON_RECONNECT_SUCCESSFUL, ""));

            resetRecState();
            Log.e(TAG, "reConnectCycled: isConnected");
            return;
        }
        Log.e(TAG, "reConnectCycled: tryRec");

        recService.schedule(() -> {
            try {
                Log.e(TAG, "reConnectCycled: *" + reCount.get() + "  times");
                callMainThread(HANDLE_EVENT_SOCKET_EVENT, new WSEvent(WSEvent.ON_RECONNECTING, ""));
                boolean res = client.reconnectBlocking();
                isConnected.set(res);
                if (res) {
                    resetRecState();
//                    sendMsg(gson.toJson(new BeanWsBase<Map>(99, BeanUser.getMap())));
                } else {
                    reCount.addAndGet(1);
                }
                Log.e(TAG, "reConnectCycled: result: *" + res);
                TimeUnit.SECONDS.sleep(reCount.get() * REC_OFFSET);
            } catch (Exception e) {
                e.printStackTrace();
                reCount.addAndGet(1);
                isConnected.set(false);
            }

        }, REC_OFFSET, TimeUnit.SECONDS);
    }

    private static void resetRecState() {
        if (recService != null && !recService.isShutdown()) {
            Log.e(TAG, "resetRecState: ");
            recService.shutdownNow();
            recService = Executors.newSingleThreadScheduledExecutor();
            reCount.set(0);
            isConnected.set(false);
        }
    }

    private static void initHeartBeat() {
//        HeartBeatTimer.start(new OnPingListener() {
//            @Override
//            public void onPing() {
//                sendMsg(PING_STR);
//
//            }
//        }, new OnPongTimeOutListener() {
//            @Override
//            public void handleTimeOut() {
//                reConnectCycled();
//            }
//        });
    }

    private static void callMainThread(int type, WSEvent msg) {
        Message message = new Message();
        message.what = type;
        message.obj = msg;
        sockEventHandler.sendMessage(message);
    }

    public static class WSEvent {
        public static final String ON_OPEN = "onOpen";
        public static final String ON_MSG = "onMsg";
        public static final String ON_CLOSE_NEED_REC = "onCloseNeedRec";
        public static final String ON_CLOSE_NO_NEED_REC = "onCloseNoNeedRec";//1006表示未发送关闭帧 需要重连
        public static final String ON_ERROR = "onError";
        public static final String ON_RECONNECTING = "onReconnecting";
        public static final String ON_RECONNECT_SUCCESSFUL = "onReconnectSuccessful";
        public static final String ON_REACH_RECONNECT_LIMIT = "onReachReconnectLimit";

        public String event, msg;

        public WSEvent(String event, String msg) {
            this.event = event;
            this.msg = msg;
        }
    }

    private static class HeartBeatTimer {
        private static Timer pingTimer, pongTimer;
        private static final int PING_INTERVAL = 10 * 1000;
        private static final int ON_PING_INTERVAL = 5 * 1000;

        public static void stop() {
            if (pongTimer != null) {
                pongTimer.cancel();
                pongTimer = null;
            }
            if (pingTimer != null) {
                pingTimer.cancel();
                pingTimer = null;
            }
        }

        public static void start(OnPingListener onPingListener, OnPongTimeOutListener onPongTimeOutListener) {
            stop();
            pingTimer = new Timer();
            pongTimer = new Timer();
            pingTimer.schedule(new TimerTask() {
                @Override
                public void run() {
                    onPingListener.onPing();
                    pongTimer.schedule(new TimerTask() {
                        @Override
                        public void run() {
                            Log.e(TAG, "handleTimeOut: ");
                            onPongTimeOutListener.handleTimeOut();
                        }
                    }, ON_PING_INTERVAL);
                }
            }, PING_INTERVAL);
        }
    }

    private interface OnPingListener {
        void onPing();
    }

    private interface OnPongTimeOutListener {
        void handleTimeOut();
    }

    private static void showReconnectDialog() {
        new AlertDialog.Builder(MainActivity.instance).setIcon(R.mipmap.ic_launcher).setTitle("websocket已断开")
                .setMessage("是否要重连？").setCancelable(false).setPositiveButton("确定", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialogInterface, int i) {
                //ToDo: 你想做的事情
                reConnectCycled();
            }
        }).setNegativeButton("取消", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialogInterface, int i) {
                //ToDo: 你想做的事情
                dialogInterface.dismiss();
            }
        }).create().show();
    }

    private static void notify_normal_singLine(String title, String content) {
        //设置想要展示的数据内容
        Intent intent = new Intent(MainActivity.instance, MainActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
        PendingIntent pIntent = PendingIntent.getActivity(MainActivity.instance,
                0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
        int smallIcon = R.drawable.ic_launcher;
        String ticker = "您有一条新通知";

        //实例化工具类，并且调用接口
        NotifyMgr notify1 = new NotifyMgr(MainActivity.instance, 1);
        notify1.notify_normal_singline(pIntent, smallIcon, ticker, title, content, true, true, false);
//        currentNotify = notify1;
    }

}
